
#include <any>
#include <ast.h>
#include <cgen.h>

#define TRUE_LABEL Pcode::label(status.top().true_label)
#define FALSE_LABEL Pcode::label(status.top().false_label)
#define CONTINUE_LABEL Pcode::label(status.top().continue_label)
#define EXIT_LABEL Pcode::label(status.top().exit_label)

void Cgen::gen(BlockStat* ptr) {
    scope.Enter();
    for (auto& stat : ptr->stats) generateStat(this, stat);
    scope.Leave();
};
void Cgen::gen(ReturnStat* ptr) {
  if (ptr->stat.has_value()) {
    generateStat(this, ptr->stat);
    codes.back().set(v0);
  }
  codes.push_back(Pcode::b(return_label));
};

void Cgen::gen(IfStat* ptr) {
    push_status(true);
    generateStat(this, ptr->cond);
    status.top().ok = false;
    codes.push_back(FALSE_LABEL);
    generateStat(this, ptr->false_stat);
    codes.push_back(Pcode::b(status.top().exit_label));
    codes.push_back(TRUE_LABEL);
    generateStat(this, ptr->true_stat);
    codes.push_back(EXIT_LABEL);
    pop_status();
};

void Cgen::gen(UnaryOpStat* ptr) {

  switch (ptr->op) {
    case kOpSelfIncSufix: {
        generateStat(this, ptr->exp);
        codes.push_back(Pcode::addiu(reg.top(), reg.top(), 1));
        VarStat var = std::any_cast<VarStat>(ptr->exp);
        std::pair<size_t, int> r = scope.find(var.name);
        codes.push_back(Pcode::sw(r.second, reg.top(), r.first));
      break;
    }
    case kOpSelfDecSufix: {
        generateStat(this, ptr->exp);
        codes.push_back(Pcode::addiu(reg.top(), reg.top(), -1));
        VarStat var = std::any_cast<VarStat>(ptr->exp);
        std::pair<size_t, int> r = scope.find(var.name);
        codes.push_back(Pcode::sw(r.second, reg.top(), r.first));
      break;
    }
    case kOpSelfIncPrefix: {
      break;
    }
    case kOpSelfDecPrefix: {
      break;
    }
    case kOpPostive: {
      break;
    }
    case kOpNegative: {
      break;
    }
    case kOpLogicNot: {
      break;
    }
    case kOpBitNot: {
      break;
    }
    case kOpTypeCast: {
      break;
    }
    case kOpPtrDeref: {
      break;
    }
    case kOpAddress: {
      break;
    }
    case kOpSizeof: {
      break;
    }
    default: break;
  }
}

#define BLOCK_START \
    generateStat(this, ptr->lhs);\
        int ra = pop();\
        generateStat(this, ptr->rhs);\
        int rb = pop();\
    
#define BLOCK_FINISH\
    push(rb); push(ra);

void Cgen::gen(BinaryOpStat* ptr) {
  switch (ptr->op) {
    case kOpFuncCall: {
      auto param = std::any_cast<FuncCallArgsStat>(ptr->rhs);
      for (size_t i = 0; i < param.args.size() && i < 4; i++) {
        push(a0+i);
        generateStat(this, param.args[i]);
        pop();
      }
      codes.push_back(Pcode::jal(std::any_cast<NameStat>(ptr->lhs).name));
      if (sizeType(ptr->type) > 0)
        codes.push_back(Pcode::move(v0, reg.top()));
      break;
    }
    case kOpArraySubscript: {
      break;
    }
    case kOpMemberAccess: {
      break;
    }
    case kOpPointer: {
      break;
    }
    case kOpTypeCast: {
      break;
    }
    case kOpDiv: {
        BLOCK_START
        codes.push_back(Pcode::vm_div(ra, rb));
        codes.push_back(Pcode::mfhi(ra));
        BLOCK_FINISH
      break;
    }
    case kOpMult: {
        BLOCK_START
        codes.push_back(Pcode::mult(ra, rb));
        codes.push_back(Pcode::mflo(ra));
        BLOCK_FINISH
      break;
    }
    case kOpReminder: {
        BLOCK_START
        codes.push_back(Pcode::vm_div(ra, rb));
        codes.push_back(Pcode::mflo(ra));
        BLOCK_FINISH
      break;
    }
    case kOpAdd: {
        BLOCK_START
        codes.push_back(Pcode::add(rb, ra, ra));
        BLOCK_FINISH
      break;
    }
    case kOpSub: {
        BLOCK_START
        codes.push_back(Pcode::sub(ra, rb, ra));
        BLOCK_FINISH
      break;
    }
    case kOpShiftLeft: {
        BLOCK_START
        BLOCK_FINISH
      break;
    }
    case kOpShiftRight: {
        BLOCK_START
        BLOCK_FINISH
      break;
    }
    case kOpGreatThan: {
      break;
    }
    case kOpGreatEqual: {
      break;
    }
    case kOpLessThan: {

      break;
    }
    case kOpLessEqual: {
      break;
    }
    case kOpEqual: {
      break;
    }
    case kOpNotEqual: {
      break;
    }
    case kOpBitAnd: {
      break;
    }
    case kOpBitXor: {
      break;
    }
    case kOpBitOr: {
      break;
    }
    case kOpLogicAnd: {

      break;
    }
    case kOpLogicOr: {
      break;
    }
    case kOpAssign: {
      VarStat var = std::any_cast<VarStat>(ptr->lhs);
      std::pair<size_t, int> r = scope.find(var.name);
      if (ptr->rhs.type() == typeid(LiteralIntegerStat) &&
          std::any_cast<LiteralIntegerStat>(ptr->rhs).val == 0) {
        codes.push_back(Pcode::sw(r.second, 0, r.first));
      } else {
        generateStat(this, ptr->rhs);
        codes.push_back(Pcode::sw(r.second, reg.top(), r.first));
      }
      break;
    }
    case kOpDivAssign: {
      break;
    }
    case kOpModAssign: {
      break;
    }
    case kOpMultAssign: {
      break;
    }
    case kOpAddAssign: {
      break;
    }
    case kOpMinusAssign: {
      break;
    }
    case kOpShiftLeftAssign: {
      break;
    }
    case kOpShiftRightAssign: {
      break;
    }
    case kOpBitAndAssign: {
      break;
    }
    case kOpBitXorAssign: {
      break;
    }
    case kOpBitOrAssign: {
      break;
    }
    case kOpComma: {
      break;
    }
    default: break;
  }

}

void Cgen::gen(TernaryOpStat* ptr) {
  push_status(true);


  pop_status();
}

void Cgen::gen(NameStat* ptr) {}

void Cgen::gen(LiteralIntegerStat* ptr) {
    if ((ptr->val & ~0xFFFF) == 0) {
        codes.push_back(Pcode::li(reg.top(), ptr->val));
    } else {
        codes.push_back(Pcode::lui(reg.top(), (ptr->val>>16)&0xFFFF));
        codes.push_back(Pcode::addiu(reg.top(), reg.top(), ptr->val&0xFFFF));
    }
}

void Cgen::gen(ParamStat *ptr) {};

void Cgen::function_init(FunctionStat *ptr) {
  size_t length = 0;
  for (auto& var : ptr->local) {
    Align(length, var.first);
  }
  size_t frame_size = (length + 7) & ~7;
  scope.init(frame_size-length, frame_size+8);
  for (auto& p : ptr->args) scope.push_param(p.second, p.first);

  max_used = used = 0;

  while (!reg.empty()) reg.pop();
  for (int i = t9; i >= t0; i--)  reg.push(i);
  push_status(false);
  return_label = label();
}
void Cgen::function_deinit() {
  scope.deinit();
  pop_status();
}

void Cgen::gen(FunctionStat* ptr) {
    function_init(ptr);

    codes.push_back(Pcode::label(ptr->name));
    codes.push_back(Pcode::addiu(sp, sp, -scope.frame_size()));

    codes.push_back(Pcode::sw(sp, ra, scope.frame_size()-4));
    codes.push_back(Pcode::sw(sp, fp, scope.frame_size()-8));

    codes.push_back(Pcode::move(sp, fp));

    for (size_t i = 0; i < ptr->args.size(); i++)  {
      std::pair<size_t, int> r = scope.find(ptr->args[i].second);
      codes.push_back(Pcode::sw(r.second, a0+i, r.first));
    }

    generateStat(this, ptr->stats);

    codes.push_back(Pcode::label(return_label));
    codes.push_back(Pcode::move(fp, sp));

    codes.push_back(Pcode::lw(sp, ra, scope.frame_size()-4));
    codes.push_back(Pcode::lw(sp, fp, scope.frame_size()-8));

    codes.push_back(Pcode::addiu(sp, sp, scope.frame_size()));
    codes.push_back(Pcode::jr(ra));

    function_deinit();
}

void Cgen::gen(VariableStat *ptr) {
  scope.push(ptr->name, ptr->type, ptr->is_static);
  if (ptr->init.has_value()) {
    if (ptr->init.type() == typeid(LiteralIntegerStat) &&
        std::any_cast<LiteralIntegerStat>(ptr->init).val == 0) {
      std::pair<size_t, int> r = scope.find(ptr->name);
      codes.push_back(Pcode::sw(r.second, 0, r.first));
    } else {
      generateStat(this, ptr->init);
      std::pair<size_t, int> r = scope.find(ptr->name);
      codes.push_back(Pcode::sw(r.second, reg.top(), r.first));
    }
  }
}

void Cgen::gen(VariableDefineStat* ptr) {
    for (auto& s : ptr->stat) generateStat(this, s);
}

void Cgen::gen(ForStat* ptr) {
    std::string cond = label();
    push_status(false);
    generateStat(this, ptr->init);
    codes.push_back(Pcode::b(cond));

    codes.push_back(TRUE_LABEL);

    generateStat(this, ptr->stat);
    codes.push_back(CONTINUE_LABEL);
    generateStat(this, ptr->step);
    codes.push_back(Pcode::label(cond));

    status.top().ok = true;
    generateStat(this, ptr->cond);
    codes.push_back(Pcode::bne(zero, reg.top(), status.top().true_label));

    codes.push_back(FALSE_LABEL);
    codes.push_back(EXIT_LABEL);
    pop_status();
}

void Cgen::gen(StructStat *ptr) {};

void Cgen::gen(VarStat *ptr) {
    int ra = pop();
    std::pair<size_t, int> r = scope.find(ptr->name);
    codes.push_back(Pcode::lw(r.second, ra, r.first));
    push(ra);
}

void Cgen::gen(GotoStat *ptr) {
    codes.push_back(Pcode::b(ptr->name));
};

void Cgen::gen(WhileStat *ptr) {
    push_status(false);
    std::string cond = label();
    codes.push_back(Pcode::b(cond));
    codes.push_back(TRUE_LABEL);
    generateStat(this, ptr->stats);

    status.top().ok = true;
    codes.push_back(Pcode::label(cond));
    generateStat(this, ptr->cond);
    codes.push_back(FALSE_LABEL);
    codes.push_back(EXIT_LABEL);

    pop_status();
}
void Cgen::gen(DoWhileStat *ptr) {
    push_status(false);

    codes.push_back(TRUE_LABEL);
    generateStat(this, ptr->stats);
    codes.push_back(CONTINUE_LABEL);
    status.top().ok = true;
    generateStat(this, ptr->cond);
    codes.push_back(FALSE_LABEL);
    codes.push_back(EXIT_LABEL);

  pop_status();
}
void Cgen::gen(BreakStat *ptr) {
    codes.push_back(Pcode::b(status.top().exit_label));
}
void Cgen::gen(ContinueStat *ptr) {
    codes.push_back(Pcode::b(status.top().continue_label));
}
void Cgen::gen(LabelStat *ptr) {
    codes.push_back(Pcode::label(ptr->name));
}



void Cgen::genType(Type ptr) {}
